///////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
#include <Material/Material.h>
#include <Material/MaterialManager.h>
#include <File/LocalFile.h>
#include <Texture/TextureManager.h>
#include <Texture/Texture.h>
#include <Shader/GPUProgram.h>
#include <Util.h>
#include <string.h>
#include <Scene/Object.h>
#include <SettingsManager.h>
#include <Scene/SceneManager.h>
#include <Scene/Camera/Camera.h>
#include <Profiling/ProfilingManager.h>

//-----------------------------------------------------------------------------

Material::Material()
{
	init();
}

//-----------------------------------------------------------------------------

Material::Material( std::string name )
{
	init();
	m_name = name;
}

//-----------------------------------------------------------------------------

void	Material::init()
{
	memset( m_ambient, 0, sizeof( m_ambient ) );
	memset( m_diffuse, 0, sizeof( m_diffuse ) );
	memset( m_specular, 0, sizeof( m_specular ) );
	memset( m_emissive, 0, sizeof( m_emissive ) );
	m_shininess = 0.0f;
	m_textures.clear();
	m_shaders.clear();
	m_activeShader = NULL;
	m_name = "";

	MaterialManager::GetInstance()->AddMaterial( this );
}

//-----------------------------------------------------------------------------

Material::~Material()
{
}

//-----------------------------------------------------------------------------

void	Material::SetName( std::string name )
{
	m_name = name;
}

//-----------------------------------------------------------------------------

std::string		Material::GetName()
{
	return m_name;
}

//-----------------------------------------------------------------------------

void	Material::Activate( Object* obj )
{
	MGL_PROFILE("Material::Activate");

	u8	shaderPass	=	SettingsManager::GetInstance()->GetShaderPass();
	m_activeShader = NULL;
	if( m_shaders.size() > shaderPass )
	{
		m_activeShader = m_shaders[shaderPass];
	}

	if( m_activeShader )
	{
		m_activeShader->Use();
	}
	
	glColor4fv( m_diffuse );
	GL_CHECK_ERRORS();

	glEnable( GL_TEXTURE_2D );
	GL_CHECK_ERRORS();

	if( m_activeShader )
	{
		m_activeShader->BeginTextureBind();
	}
	
	if( m_textures.size() )
	{
		//glEnable( GL_TEXTURE_2D );
		//GL_CHECK_ERRORS();

		for( std::map<u8,Texture*>::iterator it = m_textures.begin(); it != m_textures.end(); it++ )
		{
			it->second->bind( GL_TEXTURE0 + it->first );
			if( m_activeShader )
			{
				std::string param = m_activeShader->GetParamNameForTextureUnit( it->first );
				if( param.size() )
				{
					m_activeShader->SetParameterTexture( param.c_str(), it->second->GetTextureId(), it->first );
				}
			}
		}
	}
	//else
	//{
	//	glDisable( GL_TEXTURE_2D );
	//	GL_CHECK_ERRORS();
	//}


	if( m_activeShader )
	{
		m_activeShader->EndTextureBind();
	}

	if( obj )
	{
		Camera *cam = obj->GetSceneManager()->GetCamera();
		Matrix4x4 wv = obj->GetAbsoluteTransform() * cam->getViewMatrix();
		glLoadMatrixf( (const GLfloat*)(wv.m) );
		GL_CHECK_ERRORS();

		if( m_activeShader )
		{
			if( m_activeShader->HasParameter( "MVP" ) )
			{
				Matrix4x4 wvp = wv * cam->getProjectionMatrix();
				m_activeShader->SetParameterMatrixf( "MVP", (f32*)wvp.m );
			}

			if( m_activeShader->HasParameter( "MV" ) )
			{
				m_activeShader->SetParameterMatrixf( "MV", (f32*)wv.m );
			}

			if( m_activeShader->HasParameter( "MVIT" ) )
			{
				Matrix4x4 wvit = wv.GetInverse();
				m_activeShader->SetParameterMatrixf( "MVIT", (f32*)wvit.m );
			}

			if( m_activeShader->HasParameter( "VP" ) )
			{
				Matrix4x4 vp = cam->getWVPMatrix();
				m_activeShader->SetParameterMatrixf( "VP", (f32*)vp.m );
			}

			if( m_activeShader->HasParameter( "M" ) )
			{
				Matrix4x4 w = obj->GetAbsoluteTransform();
				m_activeShader->SetParameterMatrixf( "M", (f32*)w.m );
			}

			if( m_activeShader->HasParameter( "MI" ) )
			{
				Matrix4x4 wit = obj->GetAbsoluteTransform().GetInverse();
				m_activeShader->SetParameterMatrixf( "MI", (f32*)wit.m );
			}

			if( m_activeShader->HasParameter( "MIT" ) )
			{
				Matrix4x4 wit = obj->GetAbsoluteTransform().GetInverse().GetTranspose();
				m_activeShader->SetParameterMatrixf( "MIT", (f32*)wit.m );
			}

			if( m_activeShader->HasParameter( "V" ) )
			{
				Matrix4x4 v = cam->getViewMatrix();
				m_activeShader->SetParameterMatrixf( "V", (f32*)v.m );
			}

			if( m_activeShader->HasParameter( "VI" ) )
			{
				Matrix4x4 v = cam->getViewMatrix().GetInverse();
				m_activeShader->SetParameterMatrixf( "V", (f32*)v.m );
			}

			if( m_activeShader->HasParameter( "VIT" ) )
			{
				Matrix4x4 v = cam->getViewMatrix().GetInverse().GetTranspose();
				m_activeShader->SetParameterMatrixf( "V", (f32*)v.m );
			}

			if( m_activeShader->HasParameter( "P" ) )
			{
				Matrix4x4 p = cam->getProjectionMatrix();
				m_activeShader->SetParameterMatrixf( "P", (f32*)p.m );
			}

			if( m_activeShader->HasParameter( "PI" ) )
			{
				Matrix4x4 pi = cam->getProjectionMatrix().GetInverse();
				m_activeShader->SetParameterMatrixf( "PI", (f32*)pi.m );
			}

			if( m_activeShader->HasParameter( "PIT" ) )
			{
				Matrix4x4 pit = cam->getProjectionMatrix().GetInverse().GetTranspose();
				m_activeShader->SetParameterMatrixf( "PI", (f32*)pit.m );
			}

			if( m_activeShader->HasParameter( "camPos" ) )
			{
				Vec3f vec = cam->getPosition();
				float p[3];
				p[0] = vec.x;
				p[1] = vec.y;
				p[2] = vec.z;
				m_activeShader->SetParameter3f( "camPos", p );
			}

			if( m_activeShader->HasParameter( "distToCam" ) )
			{
				Vec3f vec = obj->getPosition() - cam->getPosition();
				f32 dist = vec.length();
				m_activeShader->SetParameter1f( "distToCam", dist );
			}
		}
	}

	if( SettingsManager::GetInstance()->GetFlag( MGLS_RENDER_WITH_SHADOW ) )
	{
		if( obj )
		{
			//TODO send with glMAtrixMode( GL_TEXTURE ); - to test
			//Matrix4x4 shadowMatrix = SettingsManager::GetInstance()->GetShadowMatrix();
			//shadowMatrix = obj->GetAbsoluteTransform() * shadowMatrix;
			//glMatrixMode( GL_TEXTURE );
			//GL_CHECK_ERRORS();
			//glLoadMatrixf( (const GLfloat*)(shadowMatrix.m) );
			//GL_CHECK_ERRORS();
			//glMatrixMode( GL_MODELVIEW );
			//GL_CHECK_ERRORS();

			if( m_activeShader && m_activeShader->HasParameter( "shadowMatrix" ) )
			{
				Matrix4x4 shadowMatrix = SettingsManager::GetInstance()->GetShadowMatrix();
				shadowMatrix = obj->GetAbsoluteTransform() * shadowMatrix;
				m_activeShader->SetParameterMatrixf( "shadowMatrix", (f32*)shadowMatrix.m );
			}
		}
	}

	sendSavedParams();
}

//-----------------------------------------------------------------------------

void	Material::Deactivate()
{
	MGL_PROFILE("Material::Deactivate");

	if( m_textures.size() )
	{
		glDisable( GL_TEXTURE_2D );
		GL_CHECK_ERRORS();
	}
	
	if( m_activeShader )
	{
		m_activeShader->Use(false);
	}
}

//-----------------------------------------------------------------------------

void Material::SetParameter1i(const char* paramName, int value)
{
	std::string param = paramName;
	m_shaderVarSaves[param] = _shader_var_(0,value);
}

//-----------------------------------------------------------------------------

void Material::SetParameter2i(const char* paramName, int* value)
{
	std::string param = paramName;
	m_shaderVarSaves[param] = _shader_var_(1,value,2);
}

//-----------------------------------------------------------------------------

void Material::SetParameter3i(const char* paramName, int* value)
{
	std::string param = paramName;
	m_shaderVarSaves[param] = _shader_var_(2,value,3);
}

//-----------------------------------------------------------------------------

void Material::SetParameter4i(const char* paramName, int* value)
{
	std::string param = paramName;
	m_shaderVarSaves[param] = _shader_var_(3,value,4);
}

//-----------------------------------------------------------------------------

void Material::SetParameter1f(const char* paramName, float value)
{
	std::string param = paramName;
	m_shaderVarSaves[param] = _shader_var_(4,value);
}

//-----------------------------------------------------------------------------

void Material::SetParameter2f(const char* paramName, float* value)
{
	std::string param = paramName;
	m_shaderVarSaves[param] = _shader_var_(5,value,2);
}

//-----------------------------------------------------------------------------

void Material::SetParameter3f(const char* paramName, float* value)
{
	std::string param = paramName;
	m_shaderVarSaves[param] = _shader_var_(6,value,3);
}

//-----------------------------------------------------------------------------

void Material::SetParameter4f(const char* paramName, float* value)
{
	std::string param = paramName;
	m_shaderVarSaves[param] = _shader_var_(7,value,4);
}

//-----------------------------------------------------------------------------

void	Material::sendSavedParams()
{
	std::map<std::string,_shader_var_>::iterator it = m_shaderVarSaves.begin();
	while( it != m_shaderVarSaves.end() )
	{
		_shader_var_ sh = it->second;
		std::string param = it->first;
		it++;

		switch(sh.func)
		{
		case 0:
			{
				m_activeShader->SetParameter1i( param.c_str(), sh.value.vInt[0] );
			}
			break;

		case 1:
			{
				m_activeShader->SetParameter2i( param.c_str(), sh.value.vInt);
			}
			break;

		case 2:
			{
				m_activeShader->SetParameter3i( param.c_str(), sh.value.vInt );
			}
			break;

		case 3:
			{
				m_activeShader->SetParameter4i( param.c_str(), sh.value.vInt );
			}
			break;

		case 4:
			{
				m_activeShader->SetParameter1f( param.c_str(), sh.value.vFloat[0] );
			}
			break;

		case 5:
			{
				m_activeShader->SetParameter2f( param.c_str(), sh.value.vFloat );
			}
			break;

		case 6:
			{
				m_activeShader->SetParameter3f( param.c_str(), sh.value.vFloat );
			}
			break;

		case 7:
			{
				m_activeShader->SetParameter4f( param.c_str(), sh.value.vFloat );
			}
			break;

		default:
			ASSERT( false && "That function in not implemnted!" );
		}
	}
}

//-----------------------------------------------------------------------------
///////////////////////////////////////////////////////////////////////////////
